package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.leyou.item.pojo.*;
import com.leyou.item.utils.CopyUtils;
import com.leyou.search.client.BrandClient;
import com.leyou.search.client.CategoryClient;
import com.leyou.search.client.GoodsClient;
import com.leyou.search.client.SpecClient;
import com.leyou.search.pojo.Goods;
import com.leyou.search.pojo.SearchRequest;
import com.leyou.search.pojo.SearchResult;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description:
 * @author: Mr_Sun
 * @Date: 2018/9/14
 * @Time: 21:44
 */
@Service
public class SearchService {
    @Autowired
    private GoodsRepository goodsRepository;
    @Autowired
    private CategoryClient categoryClient;
    @Autowired
    private BrandClient brandClient;
    @Autowired
    private GoodsClient goodsClient;
    @Autowired
    private SpecClient specClient;
    @Autowired
    private static final ObjectMapper MAPPER = new ObjectMapper();

    public SearchResult search(SearchRequest request) {
        String key = request.getKey();
        // 判断是否有搜索条件，如果没有，直接返回null。不允许搜索全部商品
        if (StringUtils.isBlank(key)) {
            return null;
        }
        Integer page = request.getPage();
        Integer size = request.getSize();
        // 构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 1、通过sourceFilter设置返回的结果字段,我们只需要id、skus、subTitle
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "skus", "subTitle"}, null));
        // 2、对key进行全文检索查询
        //MatchQueryBuilder basicQuery = QueryBuilders.matchQuery("all", key).operator(Operator.AND);
        //构建bool查询 支持品牌 分类  规格参数的过滤
        BoolQueryBuilder boolQueryBuilder = buildBoolQuery(request);
        queryBuilder.withQuery(boolQueryBuilder);
        // 3、分页
        queryBuilder.withPageable(PageRequest.of(page - 1, size));
        //分类的聚合
        String categoryAggName = "categories";
        String brandAggName = "brands";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));
        // 4、查询，获取结果
        AggregatedPage<Goods> goodsPage = (AggregatedPage<Goods>)goodsRepository.search(queryBuilder.build());
        //获取聚合并解析 写一个独立的方法 进行解析聚合结果集
        List<Map<String, Object>> categories= getCategoryAggResult(goodsPage.getAggregation(categoryAggName));
        List<Brand>  brands=   getBrandAggResult(goodsPage.getAggregation(brandAggName));
        // 封装结果并返回
        //1.总条数
        long total= goodsPage.getTotalElements();
        //总页数
      int totalPages=  (int)Math.ceil((double) total / (double) size);
        List<Map<String, Object>> paramList = null;
//        规格参数的聚合
        if (categories.size() == 1) {
            paramList= getParamAggResult((Long)categories.get(0).get("id"),boolQueryBuilder);
        }


        return new SearchResult(total,totalPages,goodsPage.getContent(),categories,brands,paramList);
    }

    private BoolQueryBuilder buildBoolQuery(SearchRequest request) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //添加基本的查询条件
        boolQueryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));
        //添加过滤条件
        if (CollectionUtils.isEmpty(request.getFilter())) {
            return boolQueryBuilder;
        }
        for (Map.Entry<String, Object> entry : request.getFilter().entrySet()) {
            String key = entry.getKey();
            //如果过滤条件是品牌,过滤的字段名是brandId
            if (StringUtils.equals("品牌", key)) {
                key = "brandId";
            } else if (StringUtils.equals("分类", key)) {
                //如果是分类,过滤的字段名:cid3
                key = "cid3";
            }else {
                key = "specs." + key + ".keyword";
            }

            //如果过滤条件是规格参数名,过滤的字段名:specs.key.keyword

            boolQueryBuilder.filter(QueryBuilders.termQuery(key, entry.getValue()));
        }

        return boolQueryBuilder;
    }

    private List<Map<String, Object>> getParamAggResult(Long id, BoolQueryBuilder basicQuery) {
        //创建自定义查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //基于基本的查询条件进行聚合
        queryBuilder.withQuery(basicQuery);
        //查询要聚合的规格参数

        //聚合规格参数
        List<SpecParam> params = this.specClient.querySpecParam(null, id, true, null);
        params.forEach(param->{
            queryBuilder.addAggregation(AggregationBuilders.terms(param.getName()).field("specs." + param.getName() + ".keyword"));
        });
        //只需要聚合结果集  不需要查询结果集
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{}, null));
        AggregatedPage<Goods> goodsPage = (AggregatedPage<Goods>) this.goodsRepository.search(queryBuilder.build());
        //解析聚合结果集 asMap()会把所有的聚合名称作为key所有的聚合内容作为value
        Map<String, Aggregation> aggregationMap = goodsPage.getAggregations().asMap();
        //定义集合  手机聚合结果集
        List<Map<String, Object>> paramMapList = new ArrayList<>();
        for (Map.Entry<String, Aggregation> entry : aggregationMap.entrySet()) {
            Map<String, Object> map = new HashMap<>();
            map.put("k", entry.getKey()); //这是把key值和k对应  目的是为了前台数据方便使用
            List<Object> options = new ArrayList<>();//定义集合收集规格参数的值

            // 解析每个桶
            StringTerms terms = (StringTerms) entry.getValue();
            //拿到每个桶
            List<StringTerms.Bucket> buckets = terms.getBuckets();
            buckets.forEach(bucket -> options.add(bucket.getKeyAsString()));//遍历每个桶把桶的值放入options集合中
            map.put("options", options);
            paramMapList.add(map);
        }
        return paramMapList;
    }

    private List<Brand> getBrandAggResult(Aggregation aggregation) {
        //拿到的是所有的brand ids所以强制转换成对应的结果集
        LongTerms terms = (LongTerms) aggregation;
        List<Long> brandsIds = terms.getBuckets().stream().map(bucket ->
             bucket.getKeyAsNumber().longValue()).collect(Collectors.toList());
        List<Brand> brandList = brandsIds.stream().map(id -> this.brandClient.queryBrandByBid(id)).collect(Collectors.toList());

        return brandList;
    }

    private List<Map<String, Object>> getCategoryAggResult(Aggregation aggregation) {
        List<Map<String, Object>> categories = new ArrayList<>();
        List<Long> categoryIds = new ArrayList<>();
        LongTerms terms = (LongTerms) aggregation;
        List<Long> cids = terms.getBuckets().stream().map(bucket -> bucket.getKeyAsNumber().longValue()).collect(Collectors.toList());
        List<String> names = this.categoryClient.queryNameByIds(cids);
        for (int i = 0; i < cids.size(); i++) {
            HashMap<String, Object> map = new HashMap<>();
            map.put("id", cids.get(i));
            map.put("name", names.get(i));
            categories.add(map);
        }
        return categories;
    }

    //构建需要的goods
    public Goods buildGoods(Spu spu) throws IOException {
        //查询商品的分类
        Goods goods = new Goods();
        try {
            //把spu属性值复制到goods对象上
            CopyUtils.Copy(spu, goods);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //  private String all; // 所有需要被搜索的信息，包含标题，分类，甚至品牌
        // 查询商品分类
        List<String> names = categoryClient.queryNameByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));
        // 查询品牌
        Brand brand = brandClient.queryBrandByBid(spu.getBrandId());
        goods.setAll(spu.getTitle() + " " + StringUtils.join(names, " ") + " " + brand.getName());
        //private String skus;sku信息的json结构
        // 查询该spu下的skus
        List<Sku> skus = goodsClient.querySkuBySpuId(spu.getId());
        // private List<Long> price;// 价格
        ArrayList<Long> prices = new ArrayList<>();
        List<Map<String, Object>> skuMapList = new ArrayList<>();
        skus.forEach(sku -> {
            prices.add(sku.getPrice());
            HashMap<String, Object> map = new HashMap<>();
            map.put("id", sku.getId());
            map.put("title", sku.getTitle());

            map.put("image", StringUtils.isNotBlank(sku.getImages()) ? StringUtils.split(sku.getImages(), ",")[0] : "");
            map.put("price", sku.getPrice());
            skuMapList.add(map);

        });
        goods.setPrice(prices);
        // 结果集中一个spu中的所有sku
        goods.setSkus(MAPPER.writeValueAsString(skuMapList));

        //    private Map<String, Object> specs;// 可搜索的规格参数，key是参数名，值是参数值
        // 查询所有搜索的规格参数
        List<SpecParam> params = specClient.querySpecParam(null, spu.getCid3(), true, null);
        // 查询spudetail
        SpuDetail spuDetail = goodsClient.querySpuDetailById(spu.getId());
        //拿到通用的spudetail 并把Jason转换成map数据结构
        Map<String, Object> genericMap = MAPPER.readValue(spuDetail.getGenericSpec(), new TypeReference<Map<String, Object>>() {
        });
        Map<String, Object> specialMap = MAPPER.readValue(spuDetail.getSpecialSpec(), new TypeReference<Map<String, Object>>() {
        });
        // 搜索规格参数及参数值集合
        HashMap<String, Object> paramMap = new HashMap<>();
        //将来我们需要   name:value  value是一个数字区间   这样的集合
        params.forEach(param -> {
            //判断是否是通用的规格参数
            if (param.getGeneric()) {
                //获取通用规格参数的值
                String value = genericMap.get(param.getId().toString()).toString();
                //判断是否是数值类型
                if (param.getNumeric()) {
                    value = chooseSegment(value, param);
                }
                paramMap.put(param.getName(), value);
            } else {
                paramMap.put(param.getName(), specialMap.get(param.getId().toString()));

            }
        });
        goods.setSpecs(paramMap);
        return goods;

    }

    private String chooseSegment(String value, SpecParam p) {
        double val = NumberUtils.toDouble(value);
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    public void saveIndex(Long id) throws IOException {
        //查询spu
        Spu spu = this.goodsClient.querySpuBySpuId(id);
        //从spu构建goods对象
        Goods goods = this.buildGoods(spu); //这里有异常必须抛出  因为这里ack交给了spring  如果不抛出  spring就给手动ack了
        //添加或者更新索引
        this.goodsRepository.save(goods);
    }

    public void deleteIndex(Long id) {
        this.goodsRepository.deleteById(id);

    }
}
